Solutions/Threat Intelligence (NEW)/Analytic Rules/FileHashEntity_CommonSecurityLog.yaml (63 lines of code) (raw):

id: 432996e9-8a93-4407-985f-13707b318a0b name: TI map File Hash to CommonSecurityLog Event description: | 'Identifies a match in CommonSecurityLog Event data from any FileHash IOC from TI' severity: Medium requiredDataConnectors: - connectorId: PaloAltoNetworks dataTypes: - CommonSecurityLog - connectorId: ThreatIntelligence dataTypes: - ThreatIntelligenceIndicator - connectorId: ThreatIntelligenceTaxii dataTypes: - ThreatIntelligenceIndicator - connectorId: MicrosoftDefenderThreatIntelligence dataTypes: - ThreatIntelligenceIndicator queryFrequency: 1h queryPeriod: 14d triggerOperator: gt triggerThreshold: 0 tactics: - CommandAndControl relevantTechniques: - T1071 query: | let dt_lookBack = 1h; let ioc_lookBack = 14d; let fileHashIndicators = ThreatIntelIndicators //extract key part of kv pair | extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0))) | where isnotempty(IndicatorType) and IndicatorType == "file" | extend FileHashType = replace("'", "", substring(ObservableKey, indexof(ObservableKey, "hashes.") + 7, strlen(ObservableKey) - indexof(ObservableKey, "hashes.") - 7)) | extend FileHashValue = toupper(ObservableValue) | extend IndicatorId = tostring(split(Id, "--")[2]) | extend Url = iff(ObservableKey == "url:value", ObservableValue, "") | where isnotempty(FileHashValue) | where TimeGenerated >= ago(ioc_lookBack) | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id | where IsActive == true and ValidUntil > now(); // Handle matches against both lower case and uppercase versions of the hash: (fileHashIndicators | extend FileHashValue = tolower(FileHashValue) | union (fileHashIndicators | extend FileHashValue = toupper(FileHashValue))) | project-reorder *, FileHashType, FileHashValue, Type // using innerunique to keep perf fast and result set low, we only need one match to indicate potential malicious activity that needs to be investigated | join kind=innerunique ( CommonSecurityLog | where TimeGenerated >= ago(dt_lookBack) | where isnotempty(FileHash) | extend CommonSecurityLog_TimeGenerated = TimeGenerated ) on $left.FileHashValue == $right.FileHash | where CommonSecurityLog_TimeGenerated < ValidUntil | summarize CommonSecurityLog_TimeGenerated = arg_max(CommonSecurityLog_TimeGenerated, *) by IndicatorId, FileHashValue | extend Description = tostring(parse_json(Data).description) | extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels)) | extend Description = tostring(parse_json(Data).description) | extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels)) | project CommonSecurityLog_TimeGenerated, Description, ActivityGroupNames, Id, ValidUntil, Confidence, SourceIP, SourcePort, DestinationIP, DestinationPort, SourceUserID, SourceUserName, DeviceName, DeviceAction, RequestURL, DestinationUserName, DestinationUserID, ApplicationProtocol, Activity, FileHashValue, FileHashType, Url | extend HostName = tostring(split(DeviceName, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(DeviceName, '.'), 1, -1), '.')) | extend Name = tostring(split(SourceUserName, '@', 0)[0]), UPNSuffix = tostring(split(SourceUserName, '@', 1)[0]) | extend timestamp = CommonSecurityLog_TimeGenerated entityMappings: - entityType: Account fieldMappings: - identifier: FullName columnName: SourceUserName - identifier: Name columnName: Name - identifier: UPNSuffix columnName: UPNSuffix - entityType: Host fieldMappings: - identifier: FullName columnName: DeviceName - identifier: HostName columnName: HostName - identifier: DnsDomain columnName: DnsDomain - entityType: IP fieldMappings: - identifier: Address columnName: SourceIP - entityType: URL fieldMappings: - identifier: Url columnName: Url - entityType: FileHash fieldMappings: - identifier: Value columnName: FileHashValue - identifier: Algorithm columnName: FileHashType version: 1.3.7 kind: Scheduled